/* json.c : dgnsc json parser
 * Copyright (C) 2009-2011 drangon <drangon.zhou@gmail.com>
 * 2011-09
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <dgnsc/json.h>
#include <dgnsc/common.h>

#include <string.h>
#include <stdlib.h>
#include <math.h>

struct array_val_t {
	int cap; // max num
	int num; // curr num
	dgn_json_t js[1]; // actually has cap + 1 size, use cap size, num valid
};

struct dgn_json_st {
	enum dgn_json_type_e type;
	union {
		int i;
		double d;
		char * s;
		struct array_val_t * arr;
		dgn_hashkv_t obj;
	} v;
};

static int clear_json( struct dgn_json_st * js )
{
	if( js->type == DGN_JSON_STRING ) {
		if( js->v.s != NULL )
			free( js->v.s );
	}
	else if( js->type == DGN_JSON_ARRAY ) {
		if( js->v.arr != NULL ) {
			int num = js->v.arr->num;
			int i;
			for( i = 0; i < num; ++i )
				dgn_json_fini( js->v.arr->js[i] );
			free( js->v.arr );
		}
	}
	else if( js->type == DGN_JSON_OBJECT ) {
		if( js->v.obj != NULL )
			dgn_hashkv_fini( js->v.obj );
	}
	js->type = DGN_JSON_NULL;
	return 0;
}

// create
dgn_json_t dgn_json_init( enum dgn_json_type_e type )
{
	struct dgn_json_st * js = (struct dgn_json_st *)malloc( sizeof(struct dgn_json_st) );
	memset( js, 0, sizeof(struct dgn_json_st) );
	js->type = type;
	return js;
}

dgn_json_t dgn_json_init_int( int val )
{
	struct dgn_json_st * js = (struct dgn_json_st *)malloc( sizeof(struct dgn_json_st) );
	js->type = DGN_JSON_INT;
	js->v.i = val;
	return js;
}

dgn_json_t dgn_json_init_double( double val )
{
	struct dgn_json_st * js = (struct dgn_json_st *)malloc( sizeof(struct dgn_json_st) );
	js->type = DGN_JSON_DOUBLE;
	js->v.d = val;
	return js;
}

dgn_json_t dgn_json_init_string( const char * str, int len )
{
	struct dgn_json_st * js = (struct dgn_json_st *)malloc( sizeof(struct dgn_json_st) );
	js->type = DGN_JSON_STRING;
	js->v.s = NULL;
	dgn_json_set_string( js, str, len );
	return js;
}

dgn_json_t dgn_json_init_dup( dgn_json_t js )
{
	if( js == NULL )
		return dgn_json_init( DGN_JSON_NULL );

	if( js->type == DGN_JSON_NULL || js->type == DGN_JSON_TRUE || js->type == DGN_JSON_FALSE ) {
		return dgn_json_init( js->type );
	}
	else if( js->type == DGN_JSON_INT ) {
		return dgn_json_init_int( js->v.i );
	}
	else if( js->type == DGN_JSON_DOUBLE ) {
		return dgn_json_init_double( js->v.d );
	}
	else if( js->type == DGN_JSON_STRING ) {
		return dgn_json_init_string( js->v.s, -1 );
	}
	else if( js->type == DGN_JSON_ARRAY ) {
		dgn_json_t new_js = dgn_json_init( DGN_JSON_ARRAY );
		if( js->v.arr != NULL && js->v.arr->num > 0 ) {
			int cap = js->v.arr->num * 9 / 8 + 1; // make a little more space
			new_js->v.arr = (struct array_val_t *)malloc( sizeof(struct array_val_t) + cap * sizeof(dgn_json_t) );
			new_js->v.arr->cap = cap;
			new_js->v.arr->num = js->v.arr->num;
			int i;
			for( i = 0; i < js->v.arr->num; ++i ) {
				new_js->v.arr->js[i] = dgn_json_init_dup( js->v.arr->js[i] );
			}
		}
		return new_js;
	}
	else if( js->type == DGN_JSON_OBJECT ) {
		dgn_json_t new_js = dgn_json_init( DGN_JSON_ARRAY );
		if( js->v.obj != NULL ) {
			new_js->v.obj = dgn_hashkv_init( dgn_hashkv_default_hash,
				dgn_hashkv_default_cmp, dgn_hashkv_default_free, 
				(dgn_hashkv_freev_func_t)dgn_json_fini );
			dgn_hashkv_iter_t iter;
			for( iter = dgn_hashkv_iter_next( js->v.obj, NULL ); iter != NULL; iter = dgn_hashkv_iter_next( js->v.obj, iter ) ) {
				char * key = dgn_hashkv_iter_get_key( js->v.obj, iter );
				dgn_json_t val = dgn_hashkv_iter_get( js->v.obj, iter );
				char * new_key = strdup( key == NULL ? "" : key );
				dgn_json_t new_val = dgn_json_init_dup( val );
				dgn_hashkv_add( new_js->v.obj, new_key, new_val );
			}
		}
		return new_js;
	}
	return dgn_json_init( DGN_JSON_NULL );
}

// get value
enum dgn_json_type_e dgn_json_get_type( dgn_json_t js )
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return DGN_JSON_NULL;
	}
	return js->type;
}

int dgn_json_get_int( dgn_json_t js ) // return 0 if wrong type
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return 0;
	}
	if( js->type == DGN_JSON_INT )
		return js->v.i;
	else if( js->type == DGN_JSON_DOUBLE )
		return (int)js->v.d;
	else if( js->type == DGN_JSON_STRING ) {
		if( js->v.s == NULL )
			return 0;
		return atoi( js->v.s );
	}
	return 0;
}

double dgn_json_get_double( dgn_json_t js )  // return 0.0 if wrong type
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return 0.0;
	}
	if( js->type == DGN_JSON_DOUBLE )
		return js->v.d;
	else if( js->type == DGN_JSON_INT )
		return (double)js->v.i;
	else if( js->type == DGN_JSON_STRING ) {
		if( js->v.s == NULL )
			return 0.0;
		return atof( js->v.s );
	}
	return 0.0;
}

const char * dgn_json_get_string( dgn_json_t js ) // return NULL if wrong type
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return NULL;
	}
	if( js->type == DGN_JSON_STRING )
		return js->v.s;
	return NULL;
}

int dgn_json_get_array_num( dgn_json_t js )
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return 0;
	}
	if( js->type != DGN_JSON_ARRAY )
		return 0;
	if( js->v.arr == NULL )
		return 0;
	return js->v.arr->num;
}

dgn_json_t dgn_json_get_array( dgn_json_t js, int index ) // return NULL if wrong type
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return NULL;
	}
	if( js->type != DGN_JSON_ARRAY )
		return NULL;
	if( index < 0 || js->v.arr == NULL || index >= js->v.arr->num )
		return NULL;
	return js->v.arr->js[index];
}

int dgn_json_get_object_num( dgn_json_t js )
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return 0;
	}
	if( js->type != DGN_JSON_OBJECT )
		return 0;
	if( js->v.obj == NULL )
		return 0;
	return dgn_hashkv_item_num( js->v.obj );
}

dgn_json_t dgn_json_get_object( dgn_json_t js, const char * key ) // return NULL if wrong type
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return NULL;
	}
	if( js->type != DGN_JSON_OBJECT )
		return NULL;
	if( js->v.obj == NULL )
		return NULL;
	return dgn_hashkv_get( js->v.obj, key );
}

// set value, automatic change type
void dgn_json_set_type( dgn_json_t js, enum dgn_json_type_e type )
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return;
	}
	if( js->type == type )
		return;
	clear_json( js );
	memset( js, 0, sizeof(struct dgn_json_st) );
	js->type = type;
	return;
}

void dgn_json_set_int( dgn_json_t js, int val )
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return;
	}
	if( js->type != DGN_JSON_INT )
		dgn_json_set_type( js, DGN_JSON_INT );
	js->v.i = val;
	return;
}

void dgn_json_set_double( dgn_json_t js, double val )
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return;
	}
	if( js->type != DGN_JSON_DOUBLE )
		dgn_json_set_type( js, DGN_JSON_DOUBLE );
	js->v.d = val;
	return;
}

void dgn_json_set_string( dgn_json_t js, const char * str, int len )
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return;
	}
	if( js->type != DGN_JSON_STRING )
		dgn_json_set_type( js, DGN_JSON_STRING );
	if( js->v.s != NULL )
		free( js->v.s );
	if( str == NULL ) {
		str = "";
		len = 0;
	}
	if( len < 0 )
		len = strlen( str );
	js->v.s = (char *)malloc( len + 1 );
	memcpy( js->v.s, str, len );
	js->v.s[len] = '\0';
	return;
}

// sub node must not belong to other, will own and release by js
// if index invalid, do nothing, return < 0, not auto change type
int dgn_json_set_array( dgn_json_t js, int index, dgn_json_t node )
{
	return dgn_json_set_array_ex( js, index, node, NULL );
}

int dgn_json_set_array_ex( dgn_json_t js, int index, dgn_json_t node, dgn_json_t * old_node )
{
	if( old_node != NULL )
		*old_node = NULL;
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return -1;
	}

	if( js->type != DGN_JSON_ARRAY )
		return -1;
	if( js->v.arr == NULL )
		return -1;
	if( index < 0 || index >= js->v.arr->num )
		return -1;

	if( old_node != NULL )
		*old_node = js->v.arr->js[index];
	else
		dgn_json_fini( js->v.arr->js[index] );
	js->v.arr->js[index] = node;
	return 0;
}

// if before_index < 0 or >= num, then insert at back, auto change type
int dgn_json_insert_array( dgn_json_t js, int before_index, dgn_json_t node )
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return -1;
	}
	if( js->type != DGN_JSON_ARRAY )
		dgn_json_set_type( js, DGN_JSON_ARRAY );

	int idx = before_index;
	if( js->v.arr == NULL ) {
		js->v.arr = (struct array_val_t *)malloc( sizeof(struct array_val_t) + 8 * sizeof(dgn_json_t) );
		js->v.arr->cap = 8;
		js->v.arr->num = 0;
	}
	if( idx < 0 || idx >= js->v.arr->num )
		idx = js->v.arr->num;
	if( idx >= js->v.arr->cap ) {
		int new_cap = js->v.arr->cap * 17 / 8;
		struct array_val_t * new_arr = (struct array_val_t *)malloc( sizeof(struct array_val_t) + new_cap * sizeof(dgn_json_t) );
		new_arr->cap = new_cap;
		new_arr->num = js->v.arr->num;
		memcpy( new_arr->js, js->v.arr->js, js->v.arr->num * sizeof(dgn_json_t) );
		free( js->v.arr );
		js->v.arr = new_arr;
	}
	if( idx != js->v.arr->num )
		memmove( js->v.arr->js + idx + 1, js->v.arr->js + idx, (js->v.arr->num - idx) * sizeof(dgn_json_t) );
	js->v.arr->js[idx] = node;
	js->v.arr->num += 1;
	return 0;
}

// if ret_node == NULL, then not return node but release it
int dgn_json_del_array( dgn_json_t js, int index, dgn_json_t * ret_node )
{
	if( ret_node != NULL )
		*ret_node = NULL;
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return -1;
	}
	if( js->type != DGN_JSON_ARRAY )
		return -1;
	if( index < 0 || js->v.arr == NULL || index >= js->v.arr->num )
		return -1;
	if( ret_node != NULL )
		*ret_node = js->v.arr->js[index];
	else
		dgn_json_fini( js->v.arr->js[index] );
	if( index != js->v.arr->num - 1 )
		memmove( js->v.arr->js + index, js->v.arr->js + index + 1, (js->v.arr->num - 1 - index) * sizeof(dgn_json_t) );
	js->v.arr->num -= 1;
	return 0;
}

int dgn_json_set_object( dgn_json_t js, const char * str, dgn_json_t node )
{
	return dgn_json_set_object_ex( js, str, -1, node, NULL );
}

int dgn_json_set_object_ex( dgn_json_t js, const char * str, int len, dgn_json_t node, dgn_json_t * old_node )
{
	if( old_node != NULL )
		*old_node = NULL;
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return -1;
	}
	if( js->type != DGN_JSON_OBJECT )
		dgn_json_set_type( js, DGN_JSON_OBJECT );

	if( str == NULL ) {
		str = "";
		len = 0;
	}
	if( len < 0 )
		len = strlen( str );
	char * key = (char *)malloc( len + 1 );
	memcpy( key, str, len );
	key[len] = '\0';

	if( js->v.obj == NULL )
		js->v.obj = dgn_hashkv_init( dgn_hashkv_default_hash,
				dgn_hashkv_default_cmp, dgn_hashkv_default_free, 
				(dgn_hashkv_freev_func_t)dgn_json_fini );
	return dgn_hashkv_add_ex( js->v.obj, key, node, NULL, (void **)old_node );
}

int dgn_json_del_object( dgn_json_t js, const char * key, dgn_json_t * ret_value )
{
	if( ret_value != NULL )
		*ret_value = NULL;
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return -1;
	}
	if( js->type != DGN_JSON_OBJECT || js->v.obj == NULL )
		return -1;
	return dgn_hashkv_del_ex( js->v.obj, key, NULL, (void **)ret_value );
}

// iterator, object only
dgn_json_iter_t dgn_json_iter_next( dgn_json_t js, dgn_json_iter_t prev )
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return NULL;
	}
	if( js->type != DGN_JSON_OBJECT || js->v.obj == NULL )
		return NULL;
	return dgn_hashkv_iter_next( js->v.obj, prev );
}

dgn_json_iter_t dgn_json_iter_find( dgn_json_t js, const char * key )
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return NULL;
	}
	if( js->type != DGN_JSON_OBJECT || js->v.obj == NULL )
		return NULL;
	return dgn_hashkv_iter_find( js->v.obj, key );
}

const char * dgn_json_iter_get_key( dgn_json_t js, dgn_json_iter_t iter )
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return NULL;
	}
	if( js->type != DGN_JSON_OBJECT || js->v.obj == NULL )
		return NULL;
	return dgn_hashkv_iter_get_key( js->v.obj, iter );
}

dgn_json_t dgn_json_iter_get( dgn_json_t js, dgn_json_iter_t iter )
{
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return NULL;
	}
	if( js->type != DGN_JSON_OBJECT || js->v.obj == NULL )
		return NULL;
	return dgn_hashkv_iter_get( js->v.obj, iter );
}

int dgn_json_iter_set( dgn_json_t js, dgn_json_iter_t iter, dgn_json_t value, dgn_json_t * old_value )
{
	if( old_value != NULL )
		*old_value = NULL;
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return -1;
	}
	if( js->type != DGN_JSON_OBJECT || js->v.obj == NULL )
		return -1;
	return dgn_hashkv_iter_set( js->v.obj, iter, value, (void **)old_value );
}

dgn_json_iter_t dgn_json_iter_del( dgn_json_t js, dgn_json_iter_t iter, dgn_json_t * old_value )
{
	if( old_value != NULL )
		*old_value = NULL;
	if( js == NULL ) {
		PR_DEBUG( "js is NULL" );
		return NULL;
	}
	if( js->type != DGN_JSON_OBJECT || js->v.obj == NULL )
		return NULL;
	return dgn_hashkv_iter_del( js->v.obj, iter, NULL, (void **)old_value );
}

static int skip_blank_comment( const char * str )
{
	int len = 0;
	while( 1 ) {
		len += (int)strspn( str + len, " \t\r\n" );
		if( str[len] == '/' && str[len + 1] == '/' ) {
			int tmp = (int)strcspn( str + len + 2, "\n" );
			len += (2 + tmp);
			if( str[len] == '\n' )
				len += 1;
		}
		else if( str[len] == '/' && str[len + 1] == '*' ) {
			const char * p = strstr( str + len + 2, "*/" );
			if( p == NULL )
				break;
			len = p - str + 2;
		}
		else {
			break;
		}
	}
	return len;
}

static int find_string_len( const char * str )
{
	int len = 0;
	while( 1 ) {
		len += (int)strcspn( str + len, "\\\"" );
		if( str[len] == '\\' ) {
			if( str[len + 1] == '\0' ) {
				len += 1;
				break;
			}
			len += 2;
			continue;
		}
		break;
	}
	return len;
}

dgn_json_t dgn_json_init_parse( const char * str, int * used_len )
{
	int len = 0;
	dgn_json_t js = NULL;

	len += skip_blank_comment( str + len );
	if( str[len] == 'n' ) {
		if( strncmp( str + len, "null", 4 ) == 0 ) {
			js = dgn_json_init( DGN_JSON_NULL );
			len += 4;
		}
	}
	else if( str[len] == 'f' ) {
		if( strncmp( str + len, "false", 5 ) == 0 ) {
			js = dgn_json_init( DGN_JSON_FALSE );
			len += 5;
		}
	}
	else if( str[len] == 't' ) {
		if( strncmp( str + len, "true", 4 ) == 0 ) {
			js = dgn_json_init( DGN_JSON_TRUE );
			len += 4;
		}
	}
	else if( str[len] == '-' || (str[len] >= '0' && str[len] <= '9') ) {
		int sign = 1;
		int is_int = 1;
		int vi = 0;
		double vd = 0.0;
		if( str[len] == '-' ) {
			sign = -1;
			len += 1;
		}
		if( str[len] == '0' ) {
			// 0 must not follow digit before '.'
			if( str[len + 1] >= '0' && str[len + 1] <= '9' ) {
				*used_len = len;
				return NULL;
			}
		}
		while( str[len] >= '0' && str[len] <= '9' ) {
			vi = vi * 10 + (str[len] - '0');
			len += 1;
		}
		if( str[len] == '.' ) {
			len += 1;
			vd = (double)vi;
			is_int = 0;
			if( str[len] < '0' || str[len] > '9' ) {
				*used_len = len;
				return NULL;
			}
			double tmp = 1.0;
			while( str[len] >= '0' && str[len] <= '9' ) {
				tmp = tmp / 10.0;
				vd = vd + (str[len] - '0') * tmp;
				len += 1;
			}
		}
		if( str[len] == 'e' || str[len] == 'E' ) {
			len += 1;
			if( is_int != 0 ) {
				is_int = 0;
				vd = (double)vi;
			}
			int p_sign = 1;
			double p_exp = 0;
			if( str[len] == '+' || str[len] == '-' ) {
				len += 1;
				if( str[len] == '-' )
					p_sign = -1;
			}
			if( str[len] < '0' || str[len] > '9' ) {
				*used_len = len;
				return NULL;
			}
			while( str[len] >= '0' && str[len] <= '9' ) {
				p_exp = p_exp * 10.0 + (str[len] - '0');
				len += 1;
			}
			p_exp *= (double)p_sign;
			vd *= pow( 10, p_exp );
		}
		if( is_int )
			js = dgn_json_init_int( sign == 1 ? vi : -vi );
		else
			js = dgn_json_init_double( vd * (double)sign );
	}
	else if( str[len] == '\"' ) {
		len += 1;
		int slen = find_string_len( str + len );
		if( str[len + slen] != '"' ) {
			*used_len = len + slen;
			return NULL;
		}
		js = dgn_json_init_string( str + len, slen );
		len += (slen + 1);
	}
	else if( str[len] == '[' ) {
		len += 1;
		js = dgn_json_init( DGN_JSON_ARRAY );
		int idx = 0;
		while( 1 ) {
			len += skip_blank_comment( str + len );
			if( str[len] == ']' ) {
				len += 1;
				break;
			}
			if( idx != 0 ) {
				if( str[len] != ',' ) {
					*used_len = len;
					dgn_json_fini( js );
					return NULL;
				}
				len += 1;
			}
			int sublen = 0;
			dgn_json_t item = dgn_json_init_parse( str + len, &sublen );
			len += sublen;
			if( item == NULL ) {
				*used_len = len;
				dgn_json_fini( js );
				return NULL;
			}
			dgn_json_insert_array( js, idx, item );
			idx += 1;
		}
	}
	else if( str[len] == '{' ) {
		len += 1;
		
		js = dgn_json_init( DGN_JSON_OBJECT );
		int idx = 0;
		while( 1 ) {
			len += skip_blank_comment( str + len );
			if( str[len] == '}' ) {
				len += 1;
				break;
			}
			if( idx != 0 ) {
				if( str[len] != ',' ) {
					*used_len = len;
					dgn_json_fini( js );
					return NULL;
				}
				len += 1;
				len += skip_blank_comment( str + len );
			}

			if( str[len] != '"' ) {
				*used_len = len;
				dgn_json_fini( js );
				return NULL;
			}
			len += 1;
			const char * key = str + len;
			int slen = find_string_len( str + len );
			if( str[len + slen] != '"' ) {
				*used_len = len + slen;
				dgn_json_fini( js );
				return NULL;
			}
			len += (slen + 1);
			len += skip_blank_comment( str + len );

			if( str[len] != ':' ) {
				*used_len = len;
				dgn_json_fini( js );
				return NULL;
			}
			len += 1;

			int sublen = 0;
			dgn_json_t item = dgn_json_init_parse( str + len, &sublen );
			len += sublen;
			if( item == NULL ) {
				*used_len = len;
				dgn_json_fini( js );
				return NULL;
			}
			dgn_json_set_object_ex( js, key, slen, item, NULL );
			idx += 1;
		}
	}
	else {
		*used_len = len;
		return NULL;
	}

	if( js != NULL ) {
		len += skip_blank_comment( str + len );
	}
	*used_len = len;
	return js;
}

int dgn_json_output( dgn_json_t js, dgn_str_t buf )
{
	if( js == NULL || buf == NULL )
		return -1;
	// dgn_str_assign_c( buf, "", -1 );
	int ret = 0;
	if( js->type == DGN_JSON_NULL ) {
		dgn_str_append_c( buf, "null", -1 );
	}
	else if( js->type == DGN_JSON_TRUE ) {
		dgn_str_append_c( buf, "true", -1 );
	}
	else if( js->type == DGN_JSON_FALSE ) {
		dgn_str_append_c( buf, "false", -1 );
	}
	else if( js->type == DGN_JSON_INT ) {
		dgn_str_append_fmt( buf, "%d", js->v.i );
	}
	else if( js->type == DGN_JSON_DOUBLE ) {
		dgn_str_append_fmt( buf, "%g", js->v.d );
	}
	else if( js->type == DGN_JSON_STRING ) {
		dgn_str_append_fmt( buf, "\"%s\"", js->v.s == NULL ? "" : js->v.s );
	}
	else if( js->type == DGN_JSON_ARRAY ) {
		dgn_str_append_c( buf, "[", -1 );
		int num = (js->v.arr == NULL) ? 0 : js->v.arr->num;
		int i;
		for( i = 0; i < num; ++i ) {
			if( i != 0 )
				dgn_str_append_c( buf, ",", -1 );
			dgn_json_output( js->v.arr->js[i], buf );
		}
		dgn_str_append_c( buf, "]", -1 );
	}
	else if( js->type == DGN_JSON_OBJECT ) {
		dgn_str_append_c( buf, "{", -1 );
		if( js->v.obj != NULL ) {
			dgn_hashkv_iter_t iter;
			int first = 1;
			for( iter = dgn_hashkv_iter_next( js->v.obj, NULL ); iter != NULL; iter = dgn_hashkv_iter_next( js->v.obj, iter ) ) {
				if( first == 1 )
					first = 0;
				else
					dgn_str_append_c( buf, ",", -1 );
				char * key = dgn_hashkv_iter_get_key( js->v.obj, iter );
				dgn_json_t val = dgn_hashkv_iter_get( js->v.obj, iter );
				dgn_str_append_fmt( buf, "\"%s\":", key == NULL ? "" : key );
				dgn_json_output( val, buf );
			}
		}
		dgn_str_append_c( buf, "}", -1 );
	}
	return ret;
}

void dgn_json_fini( dgn_json_t js )
{
	if( js == NULL )
		return;
	clear_json( js );
	free( js );
	return;
}

